<!-- Vue 模板部分：定义页面结构 -->
<template>
	<!-- Cesium 3D 地球容器，id 用于 JavaScript 中初始化 Viewer -->
	<div id="cesium-dom" class="cesium-content"></div>
</template>

<!-- TypeScript 脚本部分：使用 setup 语法糖 -->
<script setup lang="ts">
// 导入 Cesium 库的所有功能，命名为 cesium
import * as cesium from "cesium";
// 导入 Cesium 的 CSS 样式文件，用于渲染控件和界面
import "@cesium/widgets/Source/widgets.css";
// 导入直升机 3D 模型文件（GLB 格式）
import model from "@/model/bell206_helicopter.glb";

// 设置 Cesium Ion 访问令牌，用于访问 Cesium 的在线资源（地形、影像、3D Tiles 等）
cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0OTBmMzllMy0yYjgzLTQ1YmUtYjNlNi1iOGRhODdhYzkzODMiLCJpZCI6MzYxNjQyLCJpYXQiOjE3NjM1MTgzMTd9.cPtinfeWhfpRWc7Phcm--JhCSqF4pdBcVUDbi4bhvPI";

// Vue 生命周期钩子：组件挂载后执行，async 表示这是一个异步函数
onMounted(async () => {
	// 创建 Cesium Viewer 实例，这是 Cesium 的核心对象，管理整个 3D 场景
	const viewer = new cesium.Viewer("cesium-dom", {
		animation: true,        // 显示动画控制器（播放、暂停、速度控制）
		timeline: true,         // 显示时间轴（时间刻度线）
		baseLayerPicker: false, // 隐藏底图选择器（影像、地形切换按钮）
		// 配置地形数据源，使用 Cesium World Terrain（全球高精度地形）
		terrain: cesium.Terrain.fromWorldTerrain({
			requestWaterMask: true,      // 请求水体遮罩数据（用于渲染海洋、湖泊等水体）
			requestVertexNormals: true,  // 请求顶点法线数据（用于光照计算，使地形更真实）
		}),
	});

	// 启用场景光照效果，模拟太阳光照射，产生阴影和明暗效果
	viewer.scene.globe.enableLighting = true;

	/**
	 * ==================== 加载全球建筑物 3D Tiles ====================
	 * 3D Tiles 是 Cesium 的一种 3D 数据格式，用于高效流式传输和渲染大规模 3D 场景
	 */
	try {
		// 异步创建 OpenStreetMap 建筑物图层（包含全球主要城市的建筑物 3D 模型）
		const osmBuildings = await cesium.createOsmBuildingsAsync();
		// 将建筑物图层添加到场景的 primitives 集合中
		viewer.scene.primitives.add(osmBuildings);
		// 在控制台输出成功信息
		console.log('建筑物加载成功');
	} catch (error) {
		// 如果加载失败，在控制台输出警告信息和错误详情
		console.warn('建筑物加载失败:', error);
	}

	/**
	 * ==================== 建筑物点击交互功能 ====================
	 * 实现点击建筑物高亮显示和信息输出
	 */
	// 创建屏幕空间事件处理器，用于处理鼠标、触摸等输入事件
	const handler = new cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
	// 声明变量存储当前选中的建筑物，类型为 Cesium3DTileFeature 或 null
	let selectedBuilding: cesium.Cesium3DTileFeature | null = null;

	// 设置鼠标左键点击事件的回调函数
	handler.setInputAction((movement: cesium.ScreenSpaceEventHandler.PositionedEvent) => {
		// 如果之前有选中的建筑物，恢复其原始颜色（白色，透明度 0.6）
		if (selectedBuilding) {
			selectedBuilding.color = cesium.Color.WHITE.withAlpha(0.6);
		}

		// 拾取鼠标点击位置的对象（射线检测）
		const pickedFeature = viewer.scene.pick(movement.position);
		// 检查拾取的对象是否存在且是 3D Tiles 建筑物特征
		if (cesium.defined(pickedFeature) && pickedFeature instanceof cesium.Cesium3DTileFeature) {
			// 将拾取的建筑物设置为当前选中的建筑物
			selectedBuilding = pickedFeature;
			// 将选中的建筑物颜色改为亮黄色（高亮显示）
			selectedBuilding.color = cesium.Color.YELLOW;

			// 从建筑物属性中获取名称，如果没有则显示"未命名建筑"
			const name = pickedFeature.getProperty('name') || '未命名建筑';
			// 从建筑物属性中获取高度，如果没有则显示"未知"
			const height = pickedFeature.getProperty('building:height') || '未知';
			// 从建筑物属性中获取类型，如果没有则显示"未知类型"
			const buildingType = pickedFeature.getProperty('building') || '未知类型';
			// 在控制台输出建筑物信息
			console.log(`建筑: ${name}, 高度: ${height}米, 类型: ${buildingType}`);
		}
	}, cesium.ScreenSpaceEventType.LEFT_CLICK); // 指定事件类型为鼠标左键点击

	/**
	 * ==================== 获取地形高度 ====================
	 * 获取指定位置的真实地形高度，用于直升机起飞点定位
	 */
	// 定义经度（北京天安门附近）
	const longitude = 116.39;
	// 定义纬度（北京天安门附近）
	const latitude = 39.9;

	// 根据经纬度创建地理坐标对象（Cartographic 格式）
	const cartographic = cesium.Cartographic.fromDegrees(longitude, latitude);

	// 声明地形高度变量，默认值为 50 米（北京市区平均海拔）
	let terrainHeight = 50;
	try {
		// 异步采样地形高度，使用最详细的地形数据
		const terrainSamples = await cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [cartographic]);
		// 检查采样结果是否有效
		if (terrainSamples && terrainSamples.length > 0 && terrainSamples[0].height !== undefined) {
			// 将采样得到的高度赋值给 terrainHeight
			terrainHeight = terrainSamples[0].height;
		}
		// 在控制台输出获取到的地形高度
		console.log('获取到的地形高度:', terrainHeight);
	} catch (error) {
		// 如果获取失败，在控制台输出警告信息，使用默认值
		console.warn('获取地形高度失败，使用默认值:', error);
	}

	/**
	 * ==================== 创建飞行动画时间轴 ====================
	 * 设置动画的时间范围和播放模式
	 */
	// 获取当前时间作为动画开始时间（JulianDate 是 Cesium 的时间格式）
	const startTime = cesium.JulianDate.now();
	// 定义起飞阶段持续时间（10 秒）
	const takeoffDuration = 10;
	// 定义飞行阶段持续时间（360 秒 = 6 分钟）
	const flightDuration = 360;
	// 计算总持续时间（起飞 + 飞行）
	const totalDuration = takeoffDuration + flightDuration;
	// 计算动画结束时间（开始时间 + 总持续时间）
	const stopTime = cesium.JulianDate.addSeconds(startTime, totalDuration, new cesium.JulianDate());

	// 设置 Viewer 时钟的开始时间（克隆以避免引用问题）
	viewer.clock.startTime = startTime.clone();
	// 设置 Viewer 时钟的结束时间
	viewer.clock.stopTime = stopTime.clone();
	// 设置 Viewer 时钟的当前时间（从开始时间开始）
	viewer.clock.currentTime = startTime.clone();
	// 设置时钟循环模式（LOOP_STOP 表示到达结束时间后循环播放）
	viewer.clock.clockRange = cesium.ClockRange.LOOP_STOP;
	// 设置时间倍速（1 表示实时播放，2 表示 2 倍速）
	viewer.clock.multiplier = 1;

	/**
	 * ==================== 创建飞行路径 ====================
	 * 使用 SampledPositionProperty 定义直升机的位置随时间变化
	 */
	// 创建位置属性对象，用于存储不同时间点的位置样本
	const positionProperty = new cesium.SampledPositionProperty();

	// 定义圆形飞行路径的半径（0.01 度约等于 1 公里）
	const radius = 0.01;
	// 定义飞行高度（地形高度 + 100 米）
	const flightHeight = terrainHeight + 100;
	// 定义起飞点高度（地形高度 + 2 米，略高于地面）
	const groundHeight = terrainHeight + 2;

	/**
	 * -------------------- 阶段 1：垂直起飞 --------------------
	 * 直升机从地面垂直上升到飞行高度
	 */
	// 定义起飞动画的关键帧数量（5 个关键帧）
	const takeoffSteps = 5;
	// 循环生成起飞阶段的位置样本
	for (let i = 0; i <= takeoffSteps; i++) {
		// 计算当前关键帧的时间点
		const time = cesium.JulianDate.addSeconds(startTime, (i / takeoffSteps) * takeoffDuration, new cesium.JulianDate());
		// 计算当前关键帧的高度（线性插值：从 groundHeight 到 flightHeight）
		const height = groundHeight + (flightHeight - groundHeight) * (i / takeoffSteps);
		// 根据经纬度和高度创建 3D 位置（Cartesian3 格式）
		const position = cesium.Cartesian3.fromDegrees(longitude, latitude, height);
		// 将时间和位置样本添加到位置属性中
		positionProperty.addSample(time, position);
	}

	/**
	 * -------------------- 阶段 2：圆形飞行 --------------------
	 * 直升机沿着圆形路径飞行
	 */
	// 计算飞行阶段的开始时间（起飞结束后）
	const flightStartTime = cesium.JulianDate.addSeconds(startTime, takeoffDuration, new cesium.JulianDate());
	// 循环生成圆形飞行路径的位置样本（每 10 秒一个样本）
	for (let i = 0; i <= flightDuration; i += 10) {
		// 计算当前样本的时间点
		const time = cesium.JulianDate.addSeconds(flightStartTime, i, new cesium.JulianDate());
		// 计算当前样本的角度（0 到 2π，完整一圈）
		const angle = (i / flightDuration) * Math.PI * 2;

		// 使用圆的参数方程计算经度（x = centerX + radius * cos(angle)）
		const lon = longitude + radius * Math.cos(angle);
		// 使用圆的参数方程计算纬度（y = centerY + radius * sin(angle)）
		const lat = latitude + radius * Math.sin(angle);
		// 根据经纬度和飞行高度创建 3D 位置
		const position = cesium.Cartesian3.fromDegrees(lon, lat, flightHeight);

		// 将时间和位置样本添加到位置属性中
		positionProperty.addSample(time, position);
	}

	/**
	 * -------------------- 设置位置插值算法 --------------------
	 * 让 Cesium 在样本点之间自动插值，生成平滑的运动轨迹
	 */
	positionProperty.setInterpolationOptions({
		interpolationDegree: 1,                              // 插值度数（1 表示线性插值）
		interpolationAlgorithm: cesium.LinearApproximation,  // 插值算法（线性逼近）
	});

	/**
	 * ==================== 创建模型朝向 ====================
	 * 让直升机模型始终朝向运动方向
	 */
	// 创建基于速度的自动朝向属性（根据速度向量自动计算朝向）
	const velocityOrientation = new cesium.VelocityOrientationProperty(positionProperty);

	/**
	 * -------------------- 定义朝向偏移参数 --------------------
	 * 由于模型本身的朝向可能不正确，需要添加旋转偏移来修正
	 */
	// 航向角偏移（水平旋转 110 度，修正模型默认朝向）
	const headingOffset = cesium.Math.toRadians(110);
	// 俯仰角偏移（0 度，保持水平飞行）
	const pitchOffset = cesium.Math.toRadians(0);
	// 翻滚角偏移（0 度，保持水平飞行）
	const rollOffset = cesium.Math.toRadians(0);

	/**
	 * -------------------- 创建自定义朝向计算函数 --------------------
	 * 使用 CallbackProperty 动态计算每一帧的模型朝向
	 */
	const customOrientation = new cesium.CallbackProperty((time, result) => {
		// 获取基于速度的自动朝向（四元数格式）
		const velocity = velocityOrientation.getValue(time, result);
		// 如果速度朝向存在
		if (velocity) {
			// 获取当前时刻的位置
			const currentPosition = positionProperty.getValue(time);
			// 如果位置存在
			if (currentPosition) {
				// 创建欧拉角对象（heading, pitch, roll）
				const hpr = new cesium.HeadingPitchRoll(headingOffset, pitchOffset, rollOffset);
				// 将欧拉角转换为四元数（相对于当前位置）
				const offset = cesium.Transforms.headingPitchRollQuaternion(
					currentPosition,
					hpr
				);
				// 将速度朝向和偏移四元数相乘，得到最终朝向
				return cesium.Quaternion.multiply(velocity, offset, result);
			}
		}
		// 如果计算失败，返回原始速度朝向
		return velocity;
	}, false); // false 表示这个属性不是常量

	/**
	 * ==================== 创建直升机实体 ====================
	 * Entity 是 Cesium 中的实体对象，可以包含模型、位置、朝向等属性
	 */
	const entity = viewer.entities.add({
		// 设置实体的可见时间范围（只在动画时间范围内可见）
		availability: new cesium.TimeIntervalCollection([
			new cesium.TimeInterval({
				start: startTime,  // 开始时间
				stop: stopTime,    // 结束时间
			}),
		]),
		// 设置实体的位置属性（随时间变化）
		position: positionProperty,
		// 设置实体的朝向属性（随时间变化）
		orientation: customOrientation,
		// 设置 3D 模型属性
		model: {
			uri: model,              // 模型文件路径
			minimumPixelSize: 64,    // 最小像素大小（防止模型距离远时太小看不见）
			maximumScale: 200,       // 最大缩放比例（防止模型距离近时太大）
			show: true,              // 显示模型
		},
		// 设置飞行轨迹线属性
		path: {
			resolution: 1,           // 路径分辨率（越小越平滑，但性能消耗越大）
			// 设置轨迹线材质（发光线条）
			material: new cesium.PolylineGlowMaterialProperty({
				glowPower: 0.3,              // 发光强度（0-1）
				color: cesium.Color.CYAN,    // 轨迹颜色（青色）
			}),
			width: 8,                // 线宽（像素）
			leadTime: 0,             // 前方轨迹时间（0 表示不显示前方轨迹）
			trailTime: 360,          // 历史轨迹时间（360 秒，显示完整轨迹）
		},
	});

	// 在控制台输出模型位置和地形高度信息
	console.log('模型位置:', longitude, latitude, '地形高度:', terrainHeight);

	/**
	 * ==================== 多视角自动切换 ====================
	 * 在飞行过程中自动切换不同的相机视角
	 */
	// 定义多个相机视角配置（每个视角包含名称、航向角、俯仰角、距离）
	const cameraViews = [
		{ name: '后方跟随视角', heading: 0, pitch: -15, range: 200 },      // 从后方跟随直升机
		{ name: '右侧视角', heading: 90, pitch: -20, range: 250 },          // 从右侧观察直升机
		{ name: '前方视角', heading: 180, pitch: -10, range: 180 },         // 从前方迎面观察直升机
		{ name: '左侧视角', heading: -90, pitch: -25, range: 250 },         // 从左侧观察直升机
		{ name: '俯视视角', heading: 0, pitch: -90, range: 300 },           // 从上方垂直俯视直升机
		{ name: '斜后方视角', heading: 45, pitch: -30, range: 220 },        // 从斜后方观察直升机
	];

	// 声明当前视角索引变量（初始为 0，即第一个视角）
	let currentViewIndex = 0;

	/**
	 * -------------------- 初始视角设置 --------------------
	 * 在场景初始化后设置第一个视角
	 */
	setTimeout(() => {
		// 设置相机跟踪的实体（相机会自动跟随实体移动）
		viewer.trackedEntity = entity;
		// 在控制台输出提示信息
		console.log('相机开始跟踪模型，模型正在沿圆形路径飞行');
		
		// 获取第一个视角配置
		const firstView = cameraViews[0];
		// 再次设置跟踪实体（确保跟踪生效）
		viewer.trackedEntity = entity;
		// 设置相机观察点（lookAt 方法）
		viewer.scene.camera.lookAt(
			// 获取实体当前位置
			entity.position.getValue(viewer.clock.currentTime),
			// 创建相机视角参数（航向角、俯仰角、距离）
			new cesium.HeadingPitchRange(
				cesium.Math.toRadians(firstView.heading),  // 将角度转换为弧度
				cesium.Math.toRadians(firstView.pitch),    // 将角度转换为弧度
				firstView.range                             // 相机距离（米）
			)
		);
		// 在控制台输出当前视角名称
		console.log(`切换到视角: ${firstView.name}`);
	}, 100); // 延迟 100 毫秒执行（等待场景初始化完成）

	/**
	 * -------------------- 定时切换视角 --------------------
	 * 每隔 30 秒自动切换到下一个视角
	 */
	setInterval(() => {
		// 检查时钟是否正在播放动画
		if (viewer.clock.shouldAnimate) {
			// 计算下一个视角索引（循环：0, 1, 2, ..., 5, 0, 1, ...）
			currentViewIndex = (currentViewIndex + 1) % cameraViews.length;
			// 获取下一个视角配置
			const view = cameraViews[currentViewIndex];
			
			// 获取当前时钟时间
			const currentTime = viewer.clock.currentTime;
			// 获取实体在当前时间的位置
			const position = entity.position.getValue(currentTime);
			
			// 如果位置存在
			if (position) {
				// 使用 flyTo 方法平滑切换到新视角
				viewer.scene.camera.flyTo({
					destination: position,  // 目标位置
					// 相机朝向参数
					orientation: {
						heading: cesium.Math.toRadians(view.heading),  // 航向角（弧度）
						pitch: cesium.Math.toRadians(view.pitch),      // 俯仰角（弧度）
						range: view.range                               // 距离（米）
					},
					duration: 2.0,  // 切换动画持续时间（2 秒）
					// 切换完成后的回调函数
					complete: () => {
						// 重新设置跟踪实体（确保相机继续跟随）
						viewer.trackedEntity = entity;
						// 设置相机观察点
						viewer.scene.camera.lookAt(
							position,  // 观察目标位置
							// 相机视角参数
							new cesium.HeadingPitchRange(
								cesium.Math.toRadians(view.heading),  // 航向角（弧度）
								cesium.Math.toRadians(view.pitch),    // 俯仰角（弧度）
								view.range                             // 距离（米）
							)
						);
						// 在控制台输出当前视角名称
						console.log(`切换到视角: ${view.name}`);
					}
				});
			}
		}
	}, 30000);  // 定时器间隔（30000 毫秒 = 30 秒）

	// 启动时钟动画（开始播放）
	viewer.clock.shouldAnimate = true;
});
</script>

<!-- CSS 样式部分 -->
<style>
/* Cesium 容器样式 */
.cesium-content {
	width: 100%;           /* 宽度占满父容器 */
	height: 600px;         /* 高度 600 像素 */
	background-color: red; /* 背景色红色（加载时可见，加载完成后被 Cesium 覆盖） */
}
</style>
